// Copyright 2024 Mozilla Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const SQL_KEYWORDS = new Set([
  'abort',
  'abortsession',
  'abs',
  'absent',
  'absolute',
  'access',
  'accessible',
  'access_lock',
  'account',
  'acos',
  'acosh',
  'action',
  'add',
  'add_months',
  'admin',
  'after',
  'aggregate',
  'alias',
  'all',
  'allocate',
  'allow',
  'alter',
  'alterand',
  'amp',
  'analyse',
  'analyze',
  'and',
  'ansidate',
  'any',
  'any_value',
  'are',
  'array',
  'array_agg',
  'array_exists',
  'array_max_cardinality',
  'as',
  'asc',
  'asensitive',
  'asin',
  'asinh',
  'assertion',
  'associate',
  'asutime',
  'asymmetric',
  'at',
  'atan',
  'atan2',
  'atanh',
  'atomic',
  'audit',
  'authorization',
  'autoincrement',
  'aux',
  'auxiliary',
  'ave',
  'average',
  'avg',
  'backup',
  'before',
  'begin',
  'begin_frame',
  'begin_partition',
  'between',
  'binary',
  'both',
  'breadth',
  'break',
  'browse',
  'bt',
  'btrim',
  'bufferpool',
  'bulk',
  'but',
  'by',
  'call',
  'called',
  'capture',
  'cardinality',
  'cascade',
  'cascaded',
  'case',
  'casespecific',
  'case_n',
  'cast',
  'catalog',
  'ccsid',
  'cd',
  'ceil',
  'ceiling',
  'change',
  'char2hexint',
  'characters',
  'character_length',
  'chars',
  'char_length',
  'check',
  'checkpoint',
  'class',
  'classifier',
  'clob',
  'clone',
  'close',
  'cluster',
  'clustered',
  'cm',
  'coalesce',
  'collate',
  'collation',
  'collect',
  'collection',
  'collid',
  'column',
  'column_value',
  'comment',
  'commit',
  'completion',
  'compress',
  'compute',
  'concat',
  'concurrently',
  'condition',
  'connect',
  'connection',
  'constraint',
  'constraints',
  'constructor',
  'contains',
  'containstable',
  'content',
  'continue',
  'convert',
  'convert_table_header',
  'copy',
  'corr',
  'corresponding',
  'cos',
  'cosh',
  'count',
  'covar_pop',
  'covar_samp',
  'create',
  'cross',
  'cs',
  'csum',
  'ct',
  'cube',
  'cume_dist',
  'current',
  'current_catalog',
  'current_date',
  'current_default_transform_group',
  'current_lc_ctype',
  'current_path',
  'current_role',
  'current_row',
  'current_schema',
  'current_server',
  'current_time',
  'current_timestamp',
  'current_timezone',
  'current_transform_group_for_type',
  'current_user',
  'currval',
  'cursor',
  'cv',
  'cycle',
  'data',
  'database',
  'databases',
  'datablocksize',
  'dateform',
  'day',
  'days',
  'day_hour',
  'day_microsecond',
  'day_minute',
  'day_second',
  'dbcc',
  'dbinfo',
  'deallocate',
  'dec',
  'decfloat',
  'declare',
  'default',
  'deferrable',
  'deferred',
  'define',
  'degrees',
  'del',
  'delayed',
  'delete',
  'dense_rank',
  'deny',
  'depth',
  'deref',
  'desc',
  'describe',
  'descriptor',
  'destroy',
  'destructor',
  'deterministic',
  'diagnostic',
  'diagnostics',
  'dictionary',
  'disable',
  'disabled',
  'disallow',
  'disconnect',
  'disk',
  'distinct',
  'distinctrow',
  'distributed',
  'div',
  'do',
  'document',
  'domain',
  'drop',
  'dssize',
  'dual',
  'dump',
  'dynamic',
  'each',
  'echo',
  'editproc',
  'element',
  'else',
  'elseif',
  'empty',
  'enabled',
  'enclosed',
  'encoding',
  'encryption',
  'end',
  'end-exec',
  'ending',
  'end_frame',
  'end_partition',
  'eq',
  'equals',
  'erase',
  'errlvl',
  'error',
  'errorfiles',
  'errortables',
  'escape',
  'escaped',
  'et',
  'every',
  'except',
  'exception',
  'exclusive',
  'exec',
  'execute',
  'exists',
  'exit',
  'exp',
  'explain',
  'external',
  'extract',
  'fallback',
  'false',
  'fastexport',
  'fenced',
  'fetch',
  'fieldproc',
  'file',
  'fillfactor',
  'filter',
  'final',
  'first',
  'first_value',
  'floor',
  'for',
  'force',
  'foreign',
  'format',
  'found',
  'frame_row',
  'free',
  'freespace',
  'freetext',
  'freetexttable',
  'freeze',
  'from',
  'full',
  'fulltext',
  'function',
  'fusion',
  'ge',
  'general',
  'generated',
  'get',
  'give',
  'global',
  'go',
  'goto',
  'grant',
  'graphic',
  'greatest',
  'group',
  'grouping',
  'groups',
  'gt',
  'handler',
  'hash',
  'hashamp',
  'hashbakamp',
  'hashbucket',
  'hashrow',
  'having',
  'help',
  'high_priority',
  'hold',
  'holdlock',
  'host',
  'hour',
  'hours',
  'hour_microsecond',
  'hour_minute',
  'hour_second',
  'identified',
  'identity',
  'identitycol',
  'identity_insert',
  'if',
  'ignore',
  'ilike',
  'immediate',
  'in',
  'inclusive',
  'inconsistent',
  'increment',
  'index',
  'indicator',
  'infile',
  'inherit',
  'initial',
  'initialize',
  'initially',
  'initiate',
  'inner',
  'inout',
  'input',
  'ins',
  'insensitive',
  'insert',
  'instead',
  'int',
  'int1',
  'int2',
  'int3',
  'int4',
  'int8',
  'integerdate',
  'intersect',
  'intersection',
  'into',
  'io_after_gtids',
  'io_before_gtids',
  'is',
  'isnull',
  'isobid',
  'isolation',
  'iterate',
  'jar',
  'join',
  'journal',
  'json',
  'json_array',
  'json_arrayagg',
  'json_exists',
  'json_object',
  'json_objectagg',
  'json_query',
  'json_scalar',
  'json_serialize',
  'json_table',
  'json_table_primitive',
  'json_value',
  'keep',
  'key',
  'keys',
  'kill',
  'kurtosis',
  'label',
  'lag',
  'language',
  'large',
  'last',
  'last_value',
  'lateral',
  'lc_ctype',
  'le',
  'lead',
  'leading',
  'least',
  'leave',
  'left',
  'less',
  'level',
  'like',
  'like_regex',
  'limit',
  'linear',
  'lineno',
  'lines',
  'listagg',
  'ln',
  'load',
  'loading',
  'local',
  'locale',
  'localtime',
  'localtimestamp',
  'locator',
  'locators',
  'lock',
  'locking',
  'lockmax',
  'locksize',
  'log',
  'log10',
  'logging',
  'logon',
  'long',
  'longblob',
  'longtext',
  'loop',
  'lower',
  'low_priority',
  'lpad',
  'lt',
  'ltrim',
  'macro',
  'maintained',
  'map',
  'master_bind',
  'master_ssl_verify_server_cert',
  'match',
  'matches',
  'match_number',
  'match_recognize',
  'materialized',
  'mavg',
  'max',
  'maxextents',
  'maximum',
  'maxvalue',
  'mcharacters',
  'mdiff',
  'member',
  'merge',
  'method',
  'microsecond',
  'microseconds',
  'middleint',
  'min',
  'mindex',
  'minimum',
  'minus',
  'minute',
  'minutes',
  'minute_microsecond',
  'minute_second',
  'mlinreg',
  'mload',
  'mlslabel',
  'mod',
  'mode',
  'modifies',
  'modify',
  'module',
  'monitor',
  'monresource',
  'monsession',
  'month',
  'months',
  'msubstr',
  'msum',
  'multiset',
  'named',
  'names',
  'national',
  'natural',
  'nchar',
  'nclob',
  'ne',
  'nested_table_id',
  'new',
  'new_table',
  'next',
  'nextval',
  'no',
  'noaudit',
  'nocheck',
  'nocompress',
  'nonclustered',
  'none',
  'normalize',
  'not',
  'notnull',
  'nowait',
  'no_write_to_binlog',
  'nth_value',
  'ntile',
  'null',
  'nullif',
  'nullifzero',
  'nulls',
  'numparts',
  'obid',
  'object',
  'objects',
  'occurrences_regex',
  'octet_length',
  'of',
  'off',
  'offline',
  'offset',
  'offsets',
  'old',
  'old_table',
  'omit',
  'on',
  'one',
  'online',
  'only',
  'open',
  'opendatasource',
  'openquery',
  'openrowset',
  'openxml',
  'operation',
  'optimization',
  'optimize',
  'optimizer_costs',
  'option',
  'optionally',
  'or',
  'order',
  'ordinality',
  'organization',
  'out',
  'outer',
  'outfile',
  'output',
  'over',
  'overlaps',
  'overlay',
  'override',
  'package',
  'pad',
  'padded',
  'parameter',
  'parameters',
  'part',
  'partial',
  'partition',
  'partitioned',
  'partitioning',
  'password',
  'path',
  'pattern',
  'pctfree',
  'per',
  'percent',
  'percentile_cont',
  'percentile_disc',
  'percent_rank',
  'period',
  'perm',
  'permanent',
  'piecesize',
  'pivot',
  'placing',
  'plan',
  'portion',
  'position',
  'position_regex',
  'postfix',
  'power',
  'precedes',
  'prefix',
  'preorder',
  'prepare',
  'preserve',
  'prevval',
  'primary',
  'print',
  'prior',
  'priqty',
  'private',
  'privileges',
  'proc',
  'procedure',
  'profile',
  'program',
  'proportional',
  'protection',
  'psid',
  'ptf',
  'public',
  'purge',
  'qualified',
  'qualify',
  'quantile',
  'query',
  'queryno',
  'radians',
  'raiserror',
  'random',
  'range',
  'range_n',
  'rank',
  'raw',
  'read',
  'reads',
  'readtext',
  'read_write',
  'reconfigure',
  'recursive',
  'ref',
  'references',
  'referencing',
  'refresh',
  'regexp',
  'regr_avgx',
  'regr_avgy',
  'regr_count',
  'regr_intercept',
  'regr_r2',
  'regr_slope',
  'regr_sxx',
  'regr_sxy',
  'regr_syy',
  'relative',
  'release',
  'rename',
  'repeat',
  'replace',
  'replication',
  'repoverride',
  'request',
  'require',
  'resignal',
  'resource',
  'restart',
  'restore',
  'restrict',
  'result',
  'result_set_locator',
  'resume',
  'ret',
  'retrieve',
  'return',
  'returning',
  'returns',
  'revalidate',
  'revert',
  'revoke',
  'right',
  'rights',
  'rlike',
  'role',
  'rollback',
  'rollforward',
  'rollup',
  'round_ceiling',
  'round_down',
  'round_floor',
  'round_half_down',
  'round_half_even',
  'round_half_up',
  'round_up',
  'routine',
  'row',
  'rowcount',
  'rowguidcol',
  'rowid',
  'rownum',
  'rows',
  'rowset',
  'row_number',
  'rpad',
  'rule',
  'run',
  'running',
  'sample',
  'sampleid',
  'save',
  'savepoint',
  'schema',
  'schemas',
  'scope',
  'scratchpad',
  'scroll',
  'search',
  'second',
  'seconds',
  'second_microsecond',
  'secqty',
  'section',
  'security',
  'securityaudit',
  'seek',
  'sel',
  'select',
  'semantickeyphrasetable',
  'semanticsimilaritydetailstable',
  'semanticsimilaritytable',
  'sensitive',
  'separator',
  'sequence',
  'session',
  'session_user',
  'set',
  'setresrate',
  'sets',
  'setsessrate',
  'setuser',
  'share',
  'show',
  'shutdown',
  'signal',
  'similar',
  'simple',
  'sin',
  'sinh',
  'size',
  'skew',
  'skip',
  'some',
  'soundex',
  'source',
  'space',
  'spatial',
  'specific',
  'specifictype',
  'spool',
  'sql',
  'sqlexception',
  'sqlstate',
  'sqltext',
  'sqlwarning',
  'sql_big_result',
  'sql_calc_found_rows',
  'sql_small_result',
  'sqrt',
  'ss',
  'ssl',
  'standard',
  'start',
  'starting',
  'startup',
  'state',
  'statement',
  'static',
  'statistics',
  'stay',
  'stddev_pop',
  'stddev_samp',
  'stepinfo',
  'stogroup',
  'stored',
  'stores',
  'straight_join',
  'string_cs',
  'structure',
  'style',
  'submultiset',
  'subscriber',
  'subset',
  'substr',
  'substring',
  'substring_regex',
  'succeeds',
  'successful',
  'sum',
  'summary',
  'suspend',
  'symmetric',
  'synonym',
  'sysdate',
  'system',
  'system_time',
  'system_user',
  'systimestamp',
  'table',
  'tablesample',
  'tablespace',
  'tan',
  'tanh',
  'tbl_cs',
  'temporary',
  'terminate',
  'terminated',
  'textsize',
  'than',
  'then',
  'threshold',
  'timezone_hour',
  'timezone_minute',
  'title',
  'to',
  'top',
  'trace',
  'trailing',
  'tran',
  'transaction',
  'translate',
  'translate_chk',
  'translate_regex',
  'translation',
  'treat',
  'trigger',
  'trim',
  'trim_array',
  'true',
  'truncate',
  'try_convert',
  'tsequal',
  'type',
  'uc',
  'uescape',
  'uid',
  'undefined',
  'under',
  'undo',
  'union',
  'unique',
  'unknown',
  'unlock',
  'unnest',
  'unpivot',
  'unsigned',
  'until',
  'upd',
  'update',
  'updatetext',
  'upper',
  'uppercase',
  'usage',
  'use',
  'user',
  'using',
  'utc_date',
  'utc_time',
  'utc_timestamp',
  'validate',
  'validproc',
  'value',
  'values',
  'value_of',
  'varbinary',
  'varbyte',
  'varchar2',
  'varcharacter',
  'vargraphic',
  'variable',
  'variadic',
  'variant',
  'var_pop',
  'var_samp',
  'vcat',
  'verbose',
  'versioning',
  'view',
  'virtual',
  'volatile',
  'volumes',
  'wait',
  'waitfor',
  'when',
  'whenever',
  'where',
  'while',
  'width_bucket',
  'window',
  'with',
  'within',
  'within_group',
  'without',
  'wlm',
  'work',
  'write',
  'writetext',
  'xmlcast',
  'xmlexists',
  'xmlnamespaces',
  'xor',
  'year',
  'years',
  'year_month',
  'zerofill',
  'zeroifnull',
  'zone',
]);

const SQL_TYPES = new Set([
  'bigint',
  'bigserial',
  'bit',
  'blob',
  'boolean',
  'byte',
  'byteint',
  'bytes',
  'char',
  'character',
  'date',
  'datetime',
  'decimal',
  'double',
  'float',
  'float4',
  'float8',
  'integer',
  'interval',
  'mediumblob',
  'mediumint',
  'mediumtext',
  'native',
  'number',
  'numeric',
  'precision',
  'real',
  'serial',
  'smallint',
  'smallserial',
  'text',
  'time',
  'timestamp',
  'tinyblob',
  'tinyint',
  'tinytext',
  'varchar',
  'varying',
]);

class HighlightSql extends Highlighter {

  static NORMAL = 0;
  static WORD = 1;
  static QUOTE = 2;
  static QUOTE_BACKSLASH = 3;
  static DQUOTE = 4;
  static DQUOTE_BACKSLASH = 5;
  static HYPHEN = 6;
  static HYPHEN_HYPHEN = 7;
  static SLASH = 8;
  static SLASH_STAR = 9;
  static SLASH_STAR_STAR = 10;

  constructor(delegate) {
    super(delegate);
    this.word = '';
  }

  feed(input) {
    for (let i = 0; i < input.length; i += this.delta) {
      this.delta = 1;
      let c = input[i];
      switch (this.state) {

      case HighlightSql.NORMAL:
        if (!isascii(c) || isalpha(c) || c == '_') {
          this.epsilon(HighlightSql.WORD);
        } else if (c == '/') {
          this.state = HighlightSql.SLASH;
        } else if (c == '-') {
          this.state = HighlightSql.HYPHEN;
        } else if (c == '\'') {
          this.state = HighlightSql.QUOTE;
          this.push("span", "string");
          this.append(c);
        } else if (c == '"') {
          this.state = HighlightSql.DQUOTE;
          this.push("span", "string");
          this.append(c);
        } else {
          this.append(c);
        }
        break;

      case HighlightSql.WORD:
        if (!isascii(c) || isalnum(c) || c == '_' || c == '-') {
          this.word += c;
        } else {
          if (SQL_KEYWORDS.has(this.word.toLowerCase())) {
            this.push("span", "keyword");
            this.append(this.word);
            this.pop();
          } else if (SQL_TYPES.has(this.word.toLowerCase())) {
            this.push("span", "type");
            this.append(this.word);
            this.pop();
          } else {
            this.append(this.word);
          }
          this.word = '';
          this.epsilon(HighlightSql.NORMAL);
        }
        break;

      case HighlightSql.SLASH:
        if (c == '*') {
          this.push("span", "comment");
          this.append("/*");
          this.state = HighlightSql.SLASH_STAR;
        } else {
          this.append('/');
          this.epsilon(HighlightSql.NORMAL);
        }
        break;

      case HighlightSql.SLASH_STAR:
        this.append(c);
        if (c == '*')
          this.state = HighlightSql.SLASH_STAR_STAR;
        break;

      case HighlightSql.SLASH_STAR_STAR:
        this.append(c);
        if (c == '/') {
          this.pop();
          this.state = HighlightSql.NORMAL;
        } else if (c != '*') {
          this.state = HighlightSql.SLASH_STAR;
        }
        break;

      case HighlightSql.QUOTE:
        this.append(c);
        if (c == '\'') {
          this.pop();
          this.state = HighlightSql.NORMAL;
        } else if (c == '\\') {
          this.state = HighlightSql.QUOTE_BACKSLASH;
        }
        break;

      case HighlightSql.QUOTE_BACKSLASH:
        this.append(c);
        this.state = HighlightSql.QUOTE;
        break;

      case HighlightSql.DQUOTE:
        this.append(c);
        if (c == '"') {
          this.pop();
          this.state = HighlightSql.NORMAL;
        } else if (c == '\\') {
          this.state = HighlightSql.DQUOTE_BACKSLASH;
        }
        break;

      case HighlightSql.DQUOTE_BACKSLASH:
        this.append(c);
        this.state = HighlightSql.DQUOTE;
        break;

      case HighlightSql.HYPHEN:
        if (c == '-') {
          this.push("span", "comment");
          this.append("--");
          this.state = HighlightSql.HYPHEN_HYPHEN;
        } else {
          this.append('-');
          this.epsilon(HighlightSql.NORMAL);
        }
        break;

      case HighlightSql.HYPHEN_HYPHEN:
        this.append(c);
        if (c == '\n') {
          this.pop();
          this.state = HighlightSql.NORMAL;
        }
        break;

      default:
        throw new Error('Invalid state');
      }
    }
  }

  flush() {
    switch (this.state) {
    case HighlightSql.WORD:
      if (SQL_KEYWORDS.has(this.word.toLowerCase())) {
        this.push("span", "keyword");
        this.append(this.word);
        this.pop();
      } else if (SQL_TYPES.has(this.word.toLowerCase())) {
        this.push("span", "type");
        this.append(this.word);
        this.pop();
      } else {
        this.append(this.word);
      }
      this.word = '';
      break;
    case HighlightSql.SLASH:
      this.append('/');
      break;
    case HighlightSql.HYPHEN:
      this.append('-');
      break;
    case HighlightSql.QUOTE:
    case HighlightSql.QUOTE_BACKSLASH:
    case HighlightSql.DQUOTE:
    case HighlightSql.DQUOTE_BACKSLASH:
    case HighlightSql.SLASH_STAR:
    case HighlightSql.SLASH_STAR_STAR:
    case HighlightSql.HYPHEN_HYPHEN:
      this.pop();
      break;
    default:
      break;
    }
    this.state = HighlightSql.NORMAL;
    this.delegate.flush();
    this.delta = 1;
  }
}

Highlighter.REGISTRY['sql'] = HighlightSql;
